{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "06e5af38-85ad-46b6-a5ea-52a7ff947853",
   "metadata": {},
   "source": [
    "# Labelme转YOLO-Keypoint-批量\n",
    "\n",
    "同济子豪兄 2023-4-17"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0a8a2a1-fb46-4d51-8b4f-cd0568bc13f8",
   "metadata": {},
   "source": [
    "## 导入工具包"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "5969d148-19e6-4b2e-9797-01105b87b56c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import json\n",
    "import shutil\n",
    "import numpy as np\n",
    "from tqdm import tqdm"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75c4fd53-f3ce-4a27-9024-6abec83717d9",
   "metadata": {},
   "source": [
    "## 删除系统自动生成的多余文件"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7ecd3ad-874c-46cc-ac47-6f4ae3adf07a",
   "metadata": {},
   "source": [
    "### 查看待删除的多余文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "a8356247-4b89-42e9-aa87-4c630f05e1f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "!find . -iname '__MACOSX'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "a1b64d9d-bdf8-43fe-8321-bb6d241137dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "!find . -iname '.DS_Store'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "a5f038c2-72ba-4724-a190-b2ec97d8bcf0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "./【C】Labelme转YOLO-Keypoint-单个文件/.ipynb_checkpoints\n",
      "./.ipynb_checkpoints\n"
     ]
    }
   ],
   "source": [
    "!find . -iname '.ipynb_checkpoints'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "191ccc29-baeb-46ff-8125-71c49adee533",
   "metadata": {},
   "source": [
    "### 删除多余文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "4bf04f95-0750-4248-8696-fa2aa4ff03e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "!for i in `find . -iname '__MACOSX'`; do rm -rf $i;done"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "217b2325-835b-451c-89d7-3d39c5dfc5c2",
   "metadata": {},
   "outputs": [],
   "source": [
    "!for i in `find . -iname '.DS_Store'`; do rm -rf $i;done"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "cfb7afa4-106a-4e6a-ab35-86f0eb902432",
   "metadata": {},
   "outputs": [],
   "source": [
    "!for i in `find . -iname '.ipynb_checkpoints'`; do rm -rf $i;done"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a2904f2-93ba-4726-9b2b-b8285511a760",
   "metadata": {},
   "source": [
    "### 验证多余文件已删除"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "33239471-a70e-426d-beba-3517c14311e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "!find . -iname '__MACOSX'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "f82a7703-8b94-4103-9672-ec29849616b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "!find . -iname '.DS_Store'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0101460a-d0bc-4388-aa0b-d30b0f0ef538",
   "metadata": {},
   "outputs": [],
   "source": [
    "!find . -iname '.ipynb_checkpoints'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b8b9f0a-39b2-4a3f-b64f-38ee4b02d509",
   "metadata": {},
   "source": [
    "## 数据集类别信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "30a7e9be-197e-4a5a-af0b-c54226edf48d",
   "metadata": {},
   "outputs": [],
   "source": [
    "Dataset_root = 'SJB_25_Dataset'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "a33c45b4-04e5-471d-99c7-1c7bdb444ee7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 框的类别\n",
    "bbox_class = {\n",
    "    'sjb_rect':0  \n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "4ab4018b-c9da-4653-a32f-b7182ab6e023",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 关键点的类别\n",
    "keypoint_class = ['angle_30', 'angle_60', 'angle_90']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "0dd3df94-953b-4025-9965-1a749fcbb83b",
   "metadata": {},
   "outputs": [],
   "source": [
    "os.chdir(Dataset_root)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "39640f9b-ed40-4bcd-8a62-cec29e95d64d",
   "metadata": {},
   "outputs": [],
   "source": [
    "os.mkdir('labels')\n",
    "os.mkdir('labels/train')\n",
    "os.mkdir('labels/val')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "404c8abc-20d7-491c-a24c-c74d790f56c5",
   "metadata": {},
   "source": [
    "## 函数-处理单个labelme标注json文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "690e23e1-f2c6-42ae-8075-32f4f30394d5",
   "metadata": {},
   "outputs": [],
   "source": [
    "def process_single_json(labelme_path, save_folder='../../labels/train'):\n",
    "    \n",
    "    with open(labelme_path, 'r', encoding='utf-8') as f:\n",
    "        labelme = json.load(f)\n",
    "\n",
    "    img_width = labelme['imageWidth']   # 图像宽度\n",
    "    img_height = labelme['imageHeight'] # 图像高度\n",
    "\n",
    "    # 生成 YOLO 格式的 txt 文件\n",
    "    suffix = labelme_path.split('.')[-2]\n",
    "    yolo_txt_path = suffix + '.txt'\n",
    "\n",
    "    with open(yolo_txt_path, 'w', encoding='utf-8') as f:\n",
    "\n",
    "        for each_ann in labelme['shapes']: # 遍历每个标注\n",
    "\n",
    "            if each_ann['shape_type'] == 'rectangle': # 每个框，在 txt 里写一行\n",
    "\n",
    "                yolo_str = ''\n",
    "\n",
    "                ## 框的信息\n",
    "                # 框的类别 ID\n",
    "                bbox_class_id = bbox_class[each_ann['label']]\n",
    "                yolo_str += '{} '.format(bbox_class_id)\n",
    "                # 左上角和右下角的 XY 像素坐标\n",
    "                bbox_top_left_x = int(min(each_ann['points'][0][0], each_ann['points'][1][0]))\n",
    "                bbox_bottom_right_x = int(max(each_ann['points'][0][0], each_ann['points'][1][0]))\n",
    "                bbox_top_left_y = int(min(each_ann['points'][0][1], each_ann['points'][1][1]))\n",
    "                bbox_bottom_right_y = int(max(each_ann['points'][0][1], each_ann['points'][1][1]))\n",
    "                # 框中心点的 XY 像素坐标\n",
    "                bbox_center_x = int((bbox_top_left_x + bbox_bottom_right_x) / 2)\n",
    "                bbox_center_y = int((bbox_top_left_y + bbox_bottom_right_y) / 2)\n",
    "                # 框宽度\n",
    "                bbox_width = bbox_bottom_right_x - bbox_top_left_x\n",
    "                # 框高度\n",
    "                bbox_height = bbox_bottom_right_y - bbox_top_left_y\n",
    "                # 框中心点归一化坐标\n",
    "                bbox_center_x_norm = bbox_center_x / img_width\n",
    "                bbox_center_y_norm = bbox_center_y / img_height\n",
    "                # 框归一化宽度\n",
    "                bbox_width_norm = bbox_width / img_width\n",
    "                # 框归一化高度\n",
    "                bbox_height_norm = bbox_height / img_height\n",
    "\n",
    "                yolo_str += '{:.5f} {:.5f} {:.5f} {:.5f} '.format(bbox_center_x_norm, bbox_center_y_norm, bbox_width_norm, bbox_height_norm)\n",
    "\n",
    "                ## 找到该框中所有关键点，存在字典 bbox_keypoints_dict 中\n",
    "                bbox_keypoints_dict = {}\n",
    "                for each_ann in labelme['shapes']: # 遍历所有标注\n",
    "                    if each_ann['shape_type'] == 'point': # 筛选出关键点标注\n",
    "                        # 关键点XY坐标、类别\n",
    "                        x = int(each_ann['points'][0][0])\n",
    "                        y = int(each_ann['points'][0][1])\n",
    "                        label = each_ann['label']\n",
    "                        if (x>bbox_top_left_x) & (x<bbox_bottom_right_x) & (y<bbox_bottom_right_y) & (y>bbox_top_left_y): # 筛选出在该个体框中的关键点\n",
    "                            bbox_keypoints_dict[label] = [x, y]\n",
    "\n",
    "                ## 把关键点按顺序排好\n",
    "                for each_class in keypoint_class: # 遍历每一类关键点\n",
    "                    if each_class in bbox_keypoints_dict:\n",
    "                        keypoint_x_norm = bbox_keypoints_dict[each_class][0] / img_width\n",
    "                        keypoint_y_norm = bbox_keypoints_dict[each_class][1] / img_height\n",
    "                        yolo_str += '{:.5f} {:.5f} {} '.format(keypoint_x_norm, keypoint_y_norm, 2) # 2-可见不遮挡 1-遮挡 0-没有点\n",
    "                    else: # 不存在的点，一律为0\n",
    "                        yolo_str += '0 0 0 '\n",
    "                # 写入 txt 文件中\n",
    "                f.write(yolo_str + '\\n')\n",
    "                \n",
    "    shutil.move(yolo_txt_path, save_folder)\n",
    "    print('{} --> {} 转换完成'.format(labelme_path, yolo_txt_path))    "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8db18e73-840b-4075-bdb0-b818679ece19",
   "metadata": {},
   "source": [
    "## 转换训练集标注文件至`labels/train`目录"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "06c90b4d-cc48-4424-9795-74467d1f3e58",
   "metadata": {},
   "outputs": [],
   "source": [
    "os.chdir('labelme_jsons/train')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "166948af-0194-46ad-98c2-9cc4f6771cb1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DSC_0269.json --> DSC_0269.txt 转换完成\n",
      "MVIMG_20230331_080915.json --> MVIMG_20230331_080915.txt 转换完成\n",
      "DSC_0289.json --> DSC_0289.txt 转换完成\n",
      "DSC_0282.json --> DSC_0282.txt 转换完成\n",
      "DSC_0260.json --> DSC_0260.txt 转换完成\n",
      "DSC_0278.json --> DSC_0278.txt 转换完成\n",
      "MVIMG_20230331_080914.json --> MVIMG_20230331_080914.txt 转换完成\n",
      "DSC_0301.json --> DSC_0301.txt 转换完成\n",
      "DSC_0219.json --> DSC_0219.txt 转换完成\n",
      "DSC_0274.json --> DSC_0274.txt 转换完成\n",
      "DSC_0259.json --> DSC_0259.txt 转换完成\n",
      "DSC_0280.json --> DSC_0280.txt 转换完成\n",
      "DSC_0240.json --> DSC_0240.txt 转换完成\n",
      "MVIMG_20230331_080912.json --> MVIMG_20230331_080912.txt 转换完成\n",
      "DSC_0293.json --> DSC_0293.txt 转换完成\n",
      "DSC_0265.json --> DSC_0265.txt 转换完成\n",
      "DSC_0283.json --> DSC_0283.txt 转换完成\n",
      "DSC_0236.json --> DSC_0236.txt 转换完成\n",
      "DSC_0285.json --> DSC_0285.txt 转换完成\n",
      "DSC_0209.json --> DSC_0209.txt 转换完成\n",
      "YOLO格式的txt标注文件已保存至  ../../labels/train\n"
     ]
    }
   ],
   "source": [
    "save_folder = '../../labels/train'\n",
    "for labelme_path in os.listdir():\n",
    "    try:\n",
    "        process_single_json(labelme_path, save_folder=save_folder)\n",
    "    except:\n",
    "        print('******有误******', labelme_path)\n",
    "print('YOLO格式的txt标注文件已保存至 ', save_folder)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "5ed297c2-e715-42c9-80ab-fd9e886c3385",
   "metadata": {},
   "outputs": [],
   "source": [
    "os.chdir('../../')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82a4746c-8980-4763-b8b5-86b398f12b96",
   "metadata": {},
   "source": [
    "## 转换测试集标注文件至`labels/val`目录"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "46914ef6-f6a7-43a3-8270-0c5e40300c1f",
   "metadata": {},
   "outputs": [],
   "source": [
    "os.chdir('labelme_jsons/val')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "20a6ca8c-e004-46d3-abdf-b786ba9fcea2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DSC_0245.json --> DSC_0245.txt 转换完成\n",
      "MVIMG_20230331_080908.json --> MVIMG_20230331_080908.txt 转换完成\n",
      "DSC_0281.json --> DSC_0281.txt 转换完成\n",
      "DSC_0284.json --> DSC_0284.txt 转换完成\n",
      "MVIMG_20230331_080920.json --> MVIMG_20230331_080920.txt 转换完成\n",
      "YOLO格式的txt标注文件已保存至  ../../labels/val\n"
     ]
    }
   ],
   "source": [
    "save_folder = '../../labels/val'\n",
    "for labelme_path in os.listdir():\n",
    "    try:\n",
    "        process_single_json(labelme_path, save_folder=save_folder)\n",
    "    except:\n",
    "        print('******有误******', labelme_path)\n",
    "print('YOLO格式的txt标注文件已保存至 ', save_folder)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "6874920e-c01d-4ff3-8e05-2462669000cb",
   "metadata": {},
   "outputs": [],
   "source": [
    "os.chdir('../../')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02d14035-e745-403e-8519-241c9f05deaa",
   "metadata": {},
   "source": [
    "## 删除labelme格式的标注文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "9eb2bf84-99a6-42d5-b636-d10380b1018d",
   "metadata": {},
   "outputs": [],
   "source": [
    "!rm -rf labelme_jsons"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "c9411b0d-92c2-444d-a9c8-82757096fbd6",
   "metadata": {},
   "outputs": [],
   "source": [
    "os.chdir('../')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67463f0b-5125-40a6-8002-4825fec2fbda",
   "metadata": {},
   "source": [
    "## 删除系统自动生成的多余文件"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "675d52f5-658c-4c55-ae45-db1da0615200",
   "metadata": {},
   "source": [
    "### 查看待删除的多余文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "23c17bcc-bcbc-4ed1-a829-1b63e99d2b80",
   "metadata": {},
   "outputs": [],
   "source": [
    "!find . -iname '__MACOSX'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "69da9d50-11d1-4071-b818-dbb065a6474e",
   "metadata": {},
   "outputs": [],
   "source": [
    "!find . -iname '.DS_Store'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "d4d6ca60-2948-4105-b635-e0d390814067",
   "metadata": {},
   "outputs": [],
   "source": [
    "!find . -iname '.ipynb_checkpoints'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1934e50-c41f-4bbc-baf8-bd81b110558c",
   "metadata": {},
   "source": [
    "### 删除多余文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "8d959813-5703-4e01-ba3e-f27b207d59f2",
   "metadata": {},
   "outputs": [],
   "source": [
    "!for i in `find . -iname '__MACOSX'`; do rm -rf $i;done"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "d2ea0bef-6954-44d5-8478-680c8d7eded1",
   "metadata": {},
   "outputs": [],
   "source": [
    "!for i in `find . -iname '.DS_Store'`; do rm -rf $i;done"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "7a2b568c-2685-4a13-90f6-185f60066360",
   "metadata": {},
   "outputs": [],
   "source": [
    "!for i in `find . -iname '.ipynb_checkpoints'`; do rm -rf $i;done"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc18bf0c-8fb1-476b-9348-575f4ef48489",
   "metadata": {},
   "source": [
    "### 验证多余文件已删除"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "2dea6d10-e56f-4775-a055-933d09a9adde",
   "metadata": {},
   "outputs": [],
   "source": [
    "!find . -iname '__MACOSX'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "23d47236-e846-42bd-9856-a0dd07a23621",
   "metadata": {},
   "outputs": [],
   "source": [
    "!find . -iname '.DS_Store'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "a85571be-399b-4e6e-b00e-f3fea415a17e",
   "metadata": {},
   "outputs": [],
   "source": [
    "!find . -iname '.ipynb_checkpoints'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b111bfa6-55ba-48f9-891a-6545ee4df102",
   "metadata": {},
   "source": [
    "## 得到YOLO格式的关键点检测数据集"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d51d6375-4dca-4648-b193-65bbb5514f98",
   "metadata": {},
   "source": [
    "## 查看数据集目录结构"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "79e5ad47-e05d-4775-a253-1e3b9124ba41",
   "metadata": {},
   "outputs": [],
   "source": [
    "# !pip install seedir emoji"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "c0565b36-a6b7-487d-9749-648fe931fb81",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📁 SJB_25_Dataset/\n",
      "├─📁 images/\n",
      "│ ├─📁 train/\n",
      "│ └─📁 val/\n",
      "└─📁 labels/\n",
      "  ├─📁 train/\n",
      "  └─📁 val/\n"
     ]
    }
   ],
   "source": [
    "import seedir as sd\n",
    "sd.seedir('SJB_25_Dataset', style='emoji', depthlimit=2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a65519f2-f98a-47d7-982a-e0447ee7f355",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
